<!DOCTYPE html>
<html>

<head>
    <title>Customize your own components </title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.6/build/dat.gui.min.js"></script>
    <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maptalks/dist/maptalks.css">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/maptalks/dist/maptalks.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.97.0/build/three.min.js"></script>
    <script type="text/javascript"
        src="https://cdn.jsdelivr.net/npm/three-text2d@0.5.3/dist/three-text2d.min.js"></script>

    <script type="text/javascript" src="../dist/maptalks.three.js"></script>
    <script type="text/javascript" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/18639/lines.js"></script>
    <script type="text/javascript" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/18639/stops.js"></script>
    <style>
        html,
        body {
            margin: 0px;
            height: 100%;
            width: 100%;
        }

        #map {
            width: 100%;
            height: 700px;
            background-color: #000;
        }

        .header-wrap {
            position: absolute;
            top: 725px;
            display: flex;
            /* z-index: 1000; */
            align-items: center;
            justify-content: center;
            left: 25px;
            right: 25px
        }

        header {
            background: black;
            color: white;
            padding: 1em 1em 0.5em;
            font-family: Helvetica;
            font-size: 90%;
            color: rgba(255, 255, 255, 0.75);
            border-bottom: 1px solid rgba(255, 255, 255, 0.5);
            border-right: 1px solid rgba(255, 255, 255, 0.5);
            box-shadow: 2px 2px 3px black
        }

        header h1 {
            font-size: 150%;
            margin-bottom: 0.4em;
            font-weight: bold;
            color: white
        }

        div.lines {
            display: flex;
            flex-flow: row;
            margin-top: 0.8em;
            flex-wrap: wrap
        }

        .lines>span {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-right: 4px;
            margin-bottom: 4px;
            font-size: 85%;
            padding: 0.2em;
            color: white
        }

        span.A {
            background: #0039A6
        }

        span.B {
            background: #FF6319
        }

        span.G {
            background: #6CBE45
        }

        span.J {
            background: #996633
        }

        span.L {
            background: #A7A9AC
        }

        span.N {
            background: #FCCC0A;
            color: black
        }

        span.S {
            background: #808183
        }

        span.one {
            background: #EE352E
        }

        span.four {
            background: #00933C
        }

        span.seven {
            background: #B933AD
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <div class="header-wrap">
        <header>
            <h1>NYC Subway System</h1>
            <h2>Zoom for detail</h2>
            <div class="lines">
                <span class="A">A</span>
                <span class="A">C</span>
                <span class="A">E</span>
                <span class="B">B</span>
                <span class="B">D</span>
                <span class="B">F</span>
                <span class="B">M</span>
                <span class="G">G</span>
                <span class="J">J</span>
                <span class="J">Z</span>
                <span class="L">L</span>
                <span class="N">N</span>
                <span class="N">Q</span>
                <span class="N">R</span>
                <span class="S">S</span>
                <span class="one">1</span>
                <span class="one">2</span>
                <span class="one">3</span>
                <span class="four">4</span>
                <span class="four">5</span>
                <span class="four">6</span>
                <span class="seven">7</span>
            </div>
        </header>
    </div>
    <script>

        var map = new maptalks.Map("map", {
            center: [-73.96967887485732, 40.720604154845745],
            zoom: 12,
            pitch: 60,
            bearing: 57,

            centerCross: true,
            doubleClickZoom: false,
            baseLayer: new maptalks.TileLayer('tile', {
                urlTemplate: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
                subdomains: ['a', 'b', 'c', 'd'],
                attribution: '&copy; <a href="http://osm.org">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/">CARTO</a>'
            })
        });

        map.on('click', function (e) {
            console.log(e.coordinate.toArray());
        });


        var threeLayer = new maptalks.ThreeLayer('t', {
            forceRenderOnMoving: true,
            forceRenderOnRotating: true,
            // animation: true
        });


        threeLayer.prepareToDraw = function (gl, scene, camera) {
            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(0, -10, 10).normalize();
            scene.add(light);
            addSubWay();
            addSprites();
            threeLayer.config('animation', true);

        };
        threeLayer.addTo(map);


        function addSubWay() {
            var colorMap = {
                A: "#0039A6",
                B: "#FF6319",
                G: "#6CBE45",
                J: "#996633",
                L: "#A7A9AC",
                N: "#FCCC0A",
                S: "#808183",
                "1": "#EE352E",
                "4": "#00933C",
                "7": "#B933AD"
            };
            var features = lineData.features;
            var data = [];
            features.forEach(function (feature) {
                var name = feature.properties.name;
                var rt_symbol = feature.properties.rt_symbol;
                var coordinates = feature.geometry.coordinates;
                data.push({
                    lineString: new maptalks.LineString(coordinates),
                    material: new THREE.LineBasicMaterial({
                        color: colorMap[rt_symbol]
                    })
                })
            });
            var lines = data.map(function (d) {
                return threeLayer.toLine(d.lineString, {}, d.material);
            });
            threeLayer.addMesh(lines);
        }


        function getMaterial(fillColor = '#00FFFF') {
            var SIZE = 32;
            var canvas = document.createElement('canvas');
            canvas.width = canvas.height = SIZE;
            var ctx = canvas.getContext('2d');

            ctx.strokeStyle = 'white';
            ctx.lineWidth = 2;
            ctx.fillStyle = fillColor;
            ctx.arc(SIZE / 2, SIZE / 2, SIZE / 2 - 2, 0, Math.PI * 2);
            ctx.fill();

            ctx.arc(SIZE / 2, SIZE / 2, SIZE / 2 - 1, 0, Math.PI * 2);
            ctx.stroke();

            var texture = new THREE.Texture(canvas);
            texture.needsUpdate = true; //使用贴图时进行更新

            var material = new THREE.SpriteMaterial({
                map: texture,
                // side: THREE.DoubleSide,
                transparent: true
            });
            return material;
        }

        var sprites, textSprite = [];
        var material = getMaterial();
        var highlightMaterial = getMaterial('yellow');
        function addSprites() {
            // const fs = stops.features;
            var features = stops.features;
            sprites = features.map(element => {
                var { geometry, properties } = element;
                var name = properties.name;

                var sprite = new Sprite(geometry.coordinates, {
                    size: 4,
                    // altitude: Math.random() * 100,
                }, material, threeLayer);

                //tooltip test
                sprite.setToolTip(name, {
                    showTimeout: 0,
                    eventsPropagation: true,
                    dx: 10
                });

                //event test
                ['click', 'mousemove', 'mouseout', 'mouseover', 'mousedown', 'mouseup', 'dblclick', 'contextmenu'].forEach(function (eventType) {
                    sprite.on(eventType, function (e) {

                        if (e.type === 'mouseover') {
                            this.setSymbol(highlightMaterial);
                        }
                        if (e.type === 'mouseout') {
                            this.setSymbol(material);
                        }
                    });
                });
                textSprite.push(new TextSprite(geometry.coordinates, { text: name, altitude: 5 }, threeLayer));
                return sprite;
            });
            threeLayer.addMesh(sprites);
            // threeLayer.addMesh(textSprite);
            var isAdd = false;
            map.on('zoomend', function () {
                var zoom = map.getZoom();
                if (zoom >= 17 && (!isAdd)) {
                    threeLayer.addMesh(textSprite);
                    isAdd = true;
                } else if (zoom < 17 && isAdd) {
                    threeLayer.removeMesh(textSprite);
                    isAdd = false;
                }

            })
            initGui();
        }





        function initGui() {
            var params = {
                add: true,
                color: '#fff',
                show: true,
                opacity: 1,
                altitude: 0
            };

            var gui = new dat.GUI();
            gui.add(params, 'add').onChange(function () {
                if (params.add) {
                    threeLayer.addMesh(sprites);
                } else {
                    threeLayer.removeMesh(sprites);
                }
            });
            gui.addColor(params, 'color').name('color').onChange(function () {
                material.color.set(params.color);
                sprites.forEach(function (mesh) {
                    mesh.setSymbol(material);
                });
            });
            gui.add(params, 'opacity', 0, 1).onChange(function () {
                sprites.forEach(function (mesh) {
                    var material = mesh.getSymbol();
                    material.opacity = params.opacity;
                    mesh.setSymbol(material);
                });
            });
            gui.add(params, 'show').onChange(function () {
                sprites.forEach(function (mesh) {
                    if (params.show) {
                        mesh.show();
                    } else {
                        mesh.hide();
                    }
                });
            });
            gui.add(params, 'altitude', 0, 300).onChange(function () {
                sprites.forEach(function (mesh) {
                    mesh.setAltitude(params.altitude);
                });
            });
        }

        //default values
        var OPTIONS = {
            size: 10,
            altitude: 0
        };

        /**
         * custom component
         * We can think of it as a point.
         * */

        class Sprite extends maptalks.BaseObject {
            constructor(coordinate, options, material, layer) {
                options = maptalks.Util.extend({}, OPTIONS, options, { layer, coordinate });
                super();
                //Initialize internal configuration
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
                this._initOptions(options);
                const { altitude } = options;


                //Initialize internal object3d
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140
                this._createGroup();
                const sprite = new THREE.Sprite(material);
                this.getObject3d().add(sprite);

                //set object3d position
                const z = layer.distanceToVector3(altitude, altitude).x;
                const position = layer.coordinateToVector3(coordinate, z);
                this.getObject3d().position.copy(position);
            }

            getSymbol() {
                return this.getObject3d().children[0].material;
            }


            setSymbol(material) {
                if (material && material instanceof THREE.Material) {
                    material.needsUpdate = true;
                    this.getObject3d().children[0].material = material;
                }
                return this;
            }

            _animation() {
                const scale = this.getMap().getScale() / 20 / 25 * this.getOptions().size;
                this.getObject3d().children[0].scale.set(scale, scale, scale);
            }


        }



        //default values
        var OPTIONS1 = {
            fontSize: 15,
            altitude: 0,
            color: '#fff',
            text: 'hello'
        };

        /**
         * custom component
         * We can think of it as a point.
         * */

        class TextSprite extends maptalks.BaseObject {
            constructor(coordinate, options, layer) {
                options = maptalks.Util.extend({}, OPTIONS1, options, { layer, coordinate });
                super();
                //Initialize internal configuration
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
                this._initOptions(options);
                const { altitude, fontSize, color, text } = options;


                //Initialize internal object3d
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140
                this._createGroup();
                const textsprite = new THREE_Text2D.SpriteText2D(text, { align: THREE_Text2D.textAlign.center, font: `${fontSize * 2}px Arial`, fillStyle: color, antialias: false });
                this.getObject3d().add(textsprite);

                //set object3d position
                const z = layer.distanceToVector3(altitude, altitude).x;
                const position = layer.coordinateToVector3(coordinate, z);
                this.getObject3d().position.copy(position);
            }



            _animation() {
                const scale = this.getMap().getScale() / 2000 / 15 * this.getOptions().fontSize;
                this.getObject3d().children[0].scale.set(scale, scale, scale);
            }


        }





    </script>
</body>

</html>